#! /usr/bin/env python2.6
# -*- coding: utf-8 -*-

'''FileHostProtoType contains two class prototype for filehost'''

import urlparse
import urllib
import urllib2
import cookielib
import sys
import os
import re
import time
import threading
import StringIO
import gzip
import cStringIO
import traceback
import random

#3rd-part lib
import pycurl
try:
    import win32api
except:
    pass

#intra-modules
from ..poster.encode import multipart_encode
from ..poster.encode import postfile_multipart_encode
from ..poster.streaminghttp import register_openers
from ..TaskManager import UserStop

def upload_to_ftp_by_curl(url, userpwd, file, target='', target_dir='/', callback=None):
    '''
    url : ftp://host:port, without '/', without user/pwd
    userpwd : usr:pwd style
    file : file fullpath, unicode
    target : filename to save in host
    target_dir : save file to host dir, must end with '/'
    '''
    
    target = not target and os.path.basename(file).encode('utf8') or target
    url = url + target_dir + target
    
    process = pycurl.Curl()  
    
    response = StringIO.StringIO()
    process.setopt(pycurl.WRITEFUNCTION, response.write)    
    if callback is not None:
        process.setopt(pycurl.NOPROGRESS, 0)
        process.setopt(pycurl.PROGRESSFUNCTION, callback)
        
    process.setopt(pycurl.URL, url)
    process.setopt(pycurl.USERPWD, userpwd)
    process.setopt(pycurl.UPLOAD, 1)
    process.setopt(pycurl.INFILE, open(file, 'rb'))
    process.setopt(pycurl.INFILESIZE, os.path.getsize(file))
    #process.setopt(pycurl.BUFFERSIZE, 1024*64)
    process.perform()
    process.close()
    
def post_by_curl(url, data={}, cookies={}, headers={}, callback=None, rename_func=None, proxy=''):
    '''
    
    curl accept string but not unicode
    '''
    process = pycurl.Curl()    
    
    process.setopt(pycurl.URL, url)
    process.setopt(pycurl.FOLLOWLOCATION, 1)
    response = StringIO.StringIO()
    process.setopt(pycurl.WRITEFUNCTION, response.write)
    #process.setopt(pycurl.BUFFERSIZE, 1024*128)
    if callback is not None:
        process.setopt(pycurl.NOPROGRESS, 0)
        process.setopt(pycurl.PROGRESSFUNCTION, callback)
    
    if proxy:
        print proxy
        process.setopt(pycurl.PROXY, proxy.encode('utf-8'))
#        process.setopt(pycurl.PROXYPORT, 8118)
#        process.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)

    #header
    default_headers = {}
    for keyword in default_headers:
        if not headers.has_key(keyword):
            headers[keyword] = default_headers[keyword]        
    process.setopt(pycurl.HTTPHEADER, \
                   [('%s: %s' % (a, b)) for a, b in headers.iteritems()])
    
    #cookies
    parts = []
    if type(cookies) == type({}):
        for name, value in cookies.iteritems():
            parts.append('%s=%s;' % (urllib.quote_plus(name),
                                     urllib.quote_plus(value)))
            process.setopt(pycurl.COOKIE, ''.join(parts)) 
    else:
        #cookiejar
        print 'use cookiejar'
        process.setopt(pycurl.COOKIEJAR, cookies)
        
    #file-like object param    
    for key, value in data.iteritems():
        if isinstance(value, file):
            
            safefilename = value.name
            basename = os.path.basename(value.name)
            if rename_func is not None:
                basename = rename_func(value.name)
            #take care path for windows os
            if isinstance(safefilename, unicode):
                try:
                    safefilename = win32api.GetShortPathName(value.name)
                except:
                    pass
                safefilename = safefilename.encode(sys.getfilesystemencoding())                       
                basename = basename.encode('utf8')
            data[key] = (pycurl.FORM_FILE, safefilename,\
                         pycurl.FORM_FILENAME, basename)
            
    process.setopt(pycurl.HTTPPOST, data.items())
    
    process.perform()
    process.close()
    
    return response.getvalue()

def postRequest(opener, url, data={}, cb=None, retry=3, setfilename=None, setfilesize=None):
    '''
    post a request to url
    '''
    response = None
    if not data:
        request = urllib2.Request(url)
    else:
        datagen, headers = postfile_multipart_encode(data, cb=cb, \
                                                     setfilename=setfilename, setfilesize=setfilesize)
        request = urllib2.Request(url, datagen, headers)
    while(retry > 0):
        try:
            response=opener.open(request)
            break
        except UserStop:
#            traceback.print_exc()
            return None
        except:
            traceback.print_exc()
            print 'Fail to Get page data, retry...'
            print url
        time.sleep(30)
        retry -= 1
    if response is None:
        return response
    else:
        html = response.read()
        if response.headers.get('Content-Encoding') == 'gzip':
            gzipper = gzip.GzipFile(fileobj=cStringIO.StringIO(html))
            html = gzipper.read()
        html = html.decode('utf-8','ignore')
        return html

def rename_by_time_hash(name):
    filetime = time.gmtime(os.path.getmtime(name))
    filetime = time.strftime('%Y-%m-%d_', filetime)
    prefix = os.path.splitext(name)[0]
    ext = os.path.splitext(name)[1]
    #rar ext
    rar_part_ext = ''
    if ext.lower() == '.rar':
        preprefix = os.path.splitext(prefix)
        if 'part' in preprefix[1].lower():
            rar_part_ext = preprefix[1] 
            prefix = preprefix[0]
    return filetime + str(hash(prefix)) + rar_part_ext + ext

class HostItem(object):
    '''
    an object to record host file infos.
    
    Attributes:
        name: file name
        type: file or folder
        id: internal id of this file in file server
        link: link to download this file    
    '''
    def __init__(self, name, type, id, link, encrypt_name='', server_name=''):
        '''
        init a item by assign attrs
        name : original name in local
        encrypt_name : name generated by rename_func
        server_name : name saved by server(some server would rename filename contains non-ascii char)
        '''
        self.type = type
        self.id = id
        self.link = link
        
        self.name = name
        self.server_name = name
        self.encrypt_name = name  
             
        if server_name:
            self.server_name = server_name
        if encrypt_name:
            encrypt_name = os.path.split(encrypt_name)[1]
            self.encrypt_name = encrypt_name
        
    def format(self, format=''):
        '''output a formated text for pub'''
        outputname = self.name
        if self.encrypt_name and not self.encrypt_name == self.name:
            outputname = self.name + ' = ' + self.encrypt_name
                
        if format == '':
            return "%s - %s" % (outputname, self.link)
                
        if format.lower() == 'bbcode':
            return '[url="%s"]%s[/url]'%(self.link, outputname)
                
        if format.lower() == 'html':
            return '<a href="%s">%s</a>'%(self.link, outputname)
        
        if format:
            format = format.replace('%rename%', self.encrypt_name)
            format = format.replace('%name%', self.name)
            format = format.replace('%link%', self.link)
            return format

    def __cmp__(self, other):
        '''override __cmp__ funciton for sort'''
        attrs = ['name', 'server_name']
        myattrs = [getattr(self, a) for a in attrs]
        oattrs = [getattr(other, a) for a in attrs]
        return cmp(myattrs, oattrs)
        
    def __str__(self):
        '''override __str__ function for print'''
        str = "%s - %s" % (self.name, self.link)
        #__str__ have to return a string, but not unicode
        str = str.encode(sys.getfilesystemencoding(), 'ignore')
        return str
    
class FileHostProcess(object):
    '''
    ProtoType Class of Host
    
    Attributes:
        _user: accout name
        _pwd: accout pwd
        newupload: a list to save the new uploaded file infos
        existedFiles: a list to save the found file infos
        _opener: http opener object with cookie
        _cj: http cookie
        _cookieCurl: filepath of cookie saved by _cj, used by curl
        loginLock: for threading excluse when login
        fhout: output stdout to somewhere 
        fherr: output stderr to somewhere
    '''
    def __init__(self, fhout=sys.stdout, fherr=sys.stderr):
        self.other_params = {}
        self.newupload = []
        self.existedFiles = []
        self._user = ''
        self._pwd = ''
        self._opener = None
        self._cj = None
        self.wait_time = 180
        self.fhout = fhout
        self.fherr = fherr
        self._cookieCurl = ''
        self.rename_func = self.default_rename_func
        self.loginLock = threading.Lock()
        self.proxy = ''
        self.curl_proxy = ''
        self.logtype='curl'
            
    def add_other_params(self, key, value):
        self.other_params[key] = value
        
    def set_proxy(self, proxy):
        self.proxy = proxy
        
    def set_curl_proxy(self, proxy):
        self.curl_proxy = proxy
    
    def default_rename_func(self, name):
        return os.path.basename(name)
    
    def rename_by_time_hash(self, name):
        return rename_by_time_hash(name)
        
    def set_rename_func(self, rename_func):
        self.rename_func = rename_func
        
    def set_wait_time(self, wtime):
        self.wait_time = wtime
        
    def get_cookies(self):
        '''
        return a dict-format cookies
        '''
        cookies = {}
        if self._cj is None:
            return cookies
        for cookie in self._cj:
            cookies[cookie.name] = cookie.value
        return cookies
    
    def set_upload_mode(self, mode):
        if mode == 'ftp':
            self.upload_web = self.upload_to_ftp
            self.logtype = 'curl'
        else:
            self.upload_web = self.upload_web_local
            self.logtype = 'poster'
    
    def upload_file_to_ftp(self, url, userpwd, file, target='', target_dir='/', callback=None):
        upload_to_ftp_by_curl(url, userpwd, file, target, target_dir, callback)
                
    def post_file(self, url, data={}, cookies={}, headers={}, callback=None):
        '''
        post file using pycurl
        '''
        return post_by_curl(url, data, cookies, headers, callback, self.rename_func, self.curl_proxy)  
    
    def post_request(self, url, data={}, cb=None, retry=3, setfilename=None, setfilesize=None):
        '''
        send a post request using 'poster'
        '''
        return postRequest(self._opener, url, data, cb, retry, setfilename, setfilesize)      
        
    def setFhOut(self, out):
        '''Set where stdout to output'''
        self.fhout = out
        
    def setFhErr(self, err):
        '''Set where stderr to output'''
        self.fherr = err
        
    def login(self, user, pwd):
        '''
        login to the Host, subclass should implement this function to login
        '''
        raise NotImplementedError
    
    def getFileList(self):
        '''
        list all files in Host, subclass should implement this function
        '''
        raise NotImplementedError
    
    def getFolderList(self):
        '''
        list all folders in Host, subclass should implement this function
        '''
        raise NotImplementedError
    
    def find(self, filenames, filelist=None):
        found = []
        if filelist == None:
            return found      
        filelistlower = [fl.name.lower() for fl in filelist]
        todel = []
        for fl in filenames:
            try:
                index = filelistlower.index(os.path.basename(fl).lower())
                found.append(filelist[index])
                todel.append(fl)
            except:
                continue
        for d in todel:
            filenames.remove(d)
        found.sort()
        return found
    
    def findFolder(self, foldername):
        '''
        check if folder has been exsited in Host
        subclass should implement this function
        '''
        raise NotImplementedError
    
    def moveto(self, items, destFolder):
        '''
        move a few file to a destinate folder
        subclass should implement this function
        '''
        raise NotImplementedError
    
    def upload(self, file):
        '''
        upload a file to Host
        subclass should implement this function
        '''
        raise NotImplementedError
    
    def upload_web(self, file):
        '''
        upload a file to Host by simulate web submit
        subclass should implement this function
        '''
        raise NotImplementedError
    
    def loginFileHost(self, loginurl, logindata):
        '''
        loginurl : url of filehost
        logindata: a dict of data to post
        return: opener object which includes cookies
        '''        
        host = urlparse.urlparse(loginurl).hostname
        params = logindata      
        self.generateOpener()  
        self._opener.addheaders = [
                ("User-agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10 QQDownload/1.7"),
                ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
                ("Accept-Encoding","gzip,deflate"),
                ("Accept-Language","zh-cn,zh;q=0.5"),
                ("Accept-Charset","GB2312,utf-8;q=0.7,*;q=0.7"),
                ("Referer", 'http://%s/' % host),
                ("Host", host)] 
        return self.post_request(loginurl, params)
        
    def generateOpener(self):
        if self._opener is not None:
            return self._opener
        if self._cj is None:
            self._cj = cookielib.MozillaCookieJar()
        self._opener = register_openers()
        self._opener.add_handler(urllib2.HTTPCookieProcessor(self._cj))
        if self.proxy:
            proxy_support = urllib2.ProxyHandler({'http': 'http://%s' % self.proxy})
            self._opener.add_handler(proxy_support)
            self.fherr.write('Set proxy %s' % self.proxy)
        urllib2.install_opener(self._opener)
        return self._opener
    
    # convenient functions for parse htmlpage or generate post data #
    def get_upload_key(self, content, key, index, sed='"'):
        '''
        find a line contains [key] in [content]
        then split this line with [sed]
        return the [index] of splited list
        '''
        lines = content.split('\n')
        for line in lines:
            if key in line:
                tokens = line.split(sed)
                value = tokens[index]
                return value
        return ''    
    
    def get_timeticket(self, bits=13):
        timetick = int(time.time()*1000)
        add = '0'*bits
        return (str(timetick)+add)[0:bits]
    
    def get_random(self, bits=32):
        '''
        return a n-bits random hex str
        '''
        return '%032X' % random.randint(0, 1<<128)